package com.hsogoo.ultraman.repository;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;

import org.apache.zookeeper.Transaction;

import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.hsogoo.ultraman.constants.PropertySourceConstants;
import com.hsogoo.ultraman.container.ConfigContainer;
import com.hsogoo.ultraman.listener.RepositoryChangeListener;
import com.hsogoo.ultraman.utils.ConfigMetaUtil;
import com.hsogoo.ultraman.utils.ExceptionUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * Created by za-huangsaigang on 2018/9/13.
 */
@Slf4j
public class LocalFileConfigRepository extends AbstractConfigRepository implements RepositoryChangeListener {

    private static final String CONFIG_DIR = "/config-cache";
    private final String namespace;
    private File baseDir;
    private volatile Properties fileProperties;
    private volatile ConfigRepository upstream;
    private ConfigMetaUtil configMetaUtil;

    public LocalFileConfigRepository(String namespace) {
        this(namespace, null);
    }

    public LocalFileConfigRepository(String namespace, ConfigRepository upstream) {
        this.namespace = namespace;
        this.configMetaUtil = ConfigContainer.getBean(ConfigMetaUtil.class);
        this.baseDir = findLocalCacheDir();
        setUpstreamRepository(upstream);
        setLocalCacheDir(baseDir);
    }

    void setLocalCacheDir(File baseDir) {
        if (!baseDir.exists()) {
            try {
                Files.createDirectory(baseDir.toPath());
            }catch (Exception ex){
                log.warn("Unable to create local config cache directory {}, reason: {}. Will not able to cache config file.",
                        baseDir.getAbsolutePath(), ExceptionUtil.getDetailMessage(ex));
            }
        }
        assembleConfigFromUpstream();
    }

    private File findLocalCacheDir() {
        try {
            String defaultCacheDir = configMetaUtil.getDefaultLocalCacheDir();
            Path path = Paths.get(defaultCacheDir);
            if (!Files.exists(path)) {
                Files.createDirectories(path);
            }
            if (Files.exists(path) && Files.isWritable(path)) {
                return new File(defaultCacheDir, CONFIG_DIR);
            }
        } catch (Throwable ex) {
            //ignore
        }
        return new File(configMetaUtil.getClassPath(), CONFIG_DIR);
    }

    @Override
    public Properties getConfig() {
        if (fileProperties == null) {
            fileProperties = loadFromLocalCacheFile(baseDir, namespace);
        }
        Properties result = new Properties();
        result.putAll(fileProperties);
        return result;
    }

    private Properties loadFromLocalCacheFile(File baseDir, String namespace) {
        Preconditions.checkNotNull(baseDir, "Basedir cannot be null");

        File file = assembleLocalCacheFile(baseDir, namespace);
        Properties properties = null;

        if (file.isFile() && file.canRead()) {
            InputStream in = null;
            try {
                in = new FileInputStream(file);
                properties = new Properties();
                properties.load(in);
                log.info("Loading local config file {} successfully!", file.getAbsolutePath());
            } catch (IOException ex) {
                log.error(String.format("Loading config from local cache file %s failed", file.getAbsolutePath()), ex);
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException ex) {
                    // ignore
                }
            }
        } else {
            log.error(String.format("Cannot read from local cache file %s", file.getAbsolutePath()));
        }
        return properties;
    }

    @Override
    public void setUpstreamRepository(ConfigRepository upstreamConfigRepository) {
        if (upstreamConfigRepository == null) {
            return;
        }
        //clear previous listener
        if (upstream != null) {
            upstream.removeChangeListener(this);
        }
        upstream = upstreamConfigRepository;
        assembleConfigFromUpstream();
        //本地配置仓库监听远程配置仓库
        upstreamConfigRepository.addChangeListener(this);
    }

    private boolean assembleConfigFromUpstream() {
        if (upstream == null) {
            return false;
        }
        try {
            Properties properties = upstream.getConfig();
            updateFileProperties(properties);
            return true;
        } catch (Throwable ex) {
            log.error("Sync config from upstream repository {} failed, reason: {}", upstream.getClass(), ExceptionUtil.getDetailMessage(ex));
        }
        return false;
    }

    @Override
    public void onRepositoryChange(String namespace, Properties newProperties) {
        if (newProperties.equals(fileProperties)) {
            return;
        }
        log.info("{}远程配置发生变更，更新本地配置。。。", namespace);
        Properties newFileProperties = new Properties();
        newFileProperties.putAll(newProperties);
        updateFileProperties(newFileProperties);
        this.fireRepositoryChange(namespace, newProperties);
    }

    //更新本地配置文件
    private synchronized void updateFileProperties(Properties newProperties) {
        if (newProperties.equals(fileProperties)) {
            return;
        }
        this.fileProperties = newProperties;
        persistLocalCacheFile(baseDir, namespace);
    }

    //持久化本地文件
    private void persistLocalCacheFile(File baseDir, String namespace) {
        if (baseDir == null) {
            return;
        }
        File file = assembleLocalCacheFile(baseDir, namespace);

        OutputStream out = null;

        try {
            out = new FileOutputStream(file);
            fileProperties.store(out, "Persisted by DefaultConfig");
        } catch (IOException ex) {
            log.error("Persist local cache file {} failed, reason: {}.", file.getAbsolutePath(), ExceptionUtil.getDetailMessage(ex));
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException ex) {
                    //ignore
                }
            }
        }
    }

    //创建本地文件
    private File assembleLocalCacheFile(File baseDir, String namespace) {
        String fileName = String.format("%s.properties", Joiner.on(PropertySourceConstants.CONFIG_FILE_NAME_SEPARATOR)
                .join(configMetaUtil.getAppName(), configMetaUtil.getEnvName(), namespace));
        return new File(baseDir, fileName);
    }

}
